﻿#region

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using PTCN.CrossPlatform.BettingGame.CardGameFramework.Config;
using PTCN.CrossPlatform.BettingGame.CardGameFramework.Exceptions;
using PTCN.CrossPlatform.BettingGame.DataAccess.Types;
using PTCN.CrossPlatform.BettingGame.Utilities;
using PTCN.CrossPlatform.BettingGame.Utilities.Message;
using PTCN.CrossPlatform.BettingGame.Utilities.Util;
using PTCN.CrossPlatform.BettingGame.Bingo.Controllers;

#endregion

namespace PTCN.CrossPlatform.BettingGame.Bingo.Models
{
    public class SedieGameLoop : IDisposable
    {
        private Timer _roomTimer;
        private Timer _synchronizedTimer;
        private SedieRoom _room;
        private readonly object _lock = new object();
        private ConcurrentDictionary<long, SedieState> _states;
        private ConcurrentQueue<long> _registerQueue;
        private ConcurrentDictionary<long, long> _lockMoney;
        private ConcurrentDictionary<SedieGates, List<SedieBetLog>> _betLogs;
        private ConcurrentStack<SedieGates> _forbiddenGates;
        private ConcurrentDictionary<long, SedieResult> _results;
        private ConcurrentQueue<BettingGameTransaction> _tranLogs;
        private int _noBet = -1;
        private bool _quitBanker = false;
        private int _restartCount = 0;

        public SedieGates[] ForbiddenGates
        {
            get
            {
                if (_forbiddenGates == null) return new SedieGates[1];
                return _forbiddenGates.ToArray();
            }
        }

        public List<List<SedieBetLog>> BetLogs
        {
            get
            {
                if (_betLogs == null) return new List<List<SedieBetLog>>(1);

                List<List<SedieBetLog>> values = new List<List<SedieBetLog>>(6);
                for (int i = 1; i <= 6; i++)
                {
                    List<SedieBetLog> bets;
                    _betLogs.TryGetValue((SedieGates)i, out bets);

                    if (bets == null || bets.Count == 0)
                    {
                        values.Add(new List<SedieBetLog>(1));
                        continue;
                    }
                    values.Add(new List<SedieBetLog>(bets));
                }
                return values;
            }
        }

        public SediePhrases Phrase { get; private set; }
        public int Result { get; private set; }
        public int Elapsed { get; private set; }

        public long[] RegisterQueue
        {
            get
            {
                return _registerQueue == null ? new long[1] : _registerQueue.ToArray();
            }
        }

        public SedieGameLoop(SedieRoom room)
        {
            _room = room;
            _registerQueue = new ConcurrentQueue<long>();
            _states = new ConcurrentDictionary<long, SedieState>();
            _roomTimer = new Timer(ProcessGameLoop, null, Timeout.Infinite, Timeout.Infinite);
            _synchronizedTimer = new Timer(SynchronizeAction, null, Timeout.Infinite, Timeout.Infinite);
            Result = -1;
            Phrase = SediePhrases.Waiting;
        }

        private void ProcessGameLoop(object state)
        {
            if (!Monitor.TryEnter(_lock, ConfigTimeout.TryLockTimeOut)) return;
            try
            {
                switch (Phrase)
                {
                    case SediePhrases.Waiting:
                        AfterWaitingPhrase();
                        break;
                    case SediePhrases.RegisterBanker:
                        AfterRegisterPhrase();
                        break;
                    case SediePhrases.Shaking:
                        AfterShakingPhrase();
                        break;
                    case SediePhrases.Betting:
                        AfterBettingPhrase();
                        break;
                    case SediePhrases.RemoveBetting:
                        AfterRemoveBettingPhrase();
                        break;
                    case SediePhrases.ReBetting:
                        AfterReBettingPhrase();
                        break;
                    case SediePhrases.OpenPlate:
                        AfterOpenPlatePhrase();
                        break;
                    case SediePhrases.ShowResult:
                        AfterShowResultPhrase();
                        break;
                }
            }
            catch (NotEnoughMoneyException ex)
            {
                NLogManager.PublishException(ex);
                RestartGame();
            }
            catch (NotEnoughPlayerException ex)
            {
                NLogManager.PublishException(ex);
                StopTimer();
            }
            catch (Exception ex)
            {
                NLogManager.PublishException(ex);
                StopGamePlay(CommonClientMessage.UnexpectedException);
            }
            finally
            {
                Monitor.Exit(_lock);
            }
        }

        #region step

        /// <summary>
        /// Sau khi het thoi gian cho, update so tien nguoi choi va tao phien
        /// </summary>
        private void AfterWaitingPhrase()
        {
            if (!InitSession()) throw new Exception();

            long roomId = _room.SessionId;
            long bankerReq = PenaltyMoney.GetBankerReqMoney(_room.MinBet, _room.CountPlayers);

            //Check regQueue to remove invalid accountid
            List<long> temp = _registerQueue.ToList();
            temp.RemoveAll(id => !_room.PlayersInGame.Contains(id));
            _registerQueue = new ConcurrentQueue<long>(temp);

            //Neu phien da co nha cai
            if (_room.PlayersInGame.Contains(_room.Banker))
            {
                SediePlayer player = _room.GetPlayer(_room.Banker) as SediePlayer;
                if (player.Account.IsEnoughMoney(bankerReq, _room.MoneyType))
                {
                    //Sang phien xoc dia va thong bao cho clients
                    NotifyChangePhrase(SediePhrases.Shaking);
                    return;
                }
                else
                {
                    player.IsBanker = false;
                    _room.Banker = -1;
                }
            }

            //Neu ko co nguoi dang ki lam nha cai -> chuyen sang phien dang ki nha cai
            _room.Banker = -1;
            if (_registerQueue.IsEmpty)
            {
                NotifyChangePhrase(SediePhrases.RegisterBanker);
                return;
            }

            //Neu da co nguoi dang ki lam nha cai -> duyet va kiem tra
            while (!_registerQueue.IsEmpty)
            {
                long accountId;
                if (!_registerQueue.TryDequeue(out accountId)) continue;
                //Dang ki nhung ko co, next
                if (!_room.PlayersInGame.Contains(accountId)) continue;
                //Dang ki nhung ko du tien
                SediePlayer player = _room.GetPlayer(accountId) as SediePlayer;
                if (!player.Account.IsEnoughMoney(bankerReq, _room.MoneyType)) continue;
                _room.Banker = accountId;
                player.IsBanker = true;
                break;
            }

            //Neu van khong co nguoi du dieu kien lam cai -> sang phien dang ki lam cai
            if (_room.Banker < 1)
            {
                NotifyChangePhrase(SediePhrases.RegisterBanker);
                return;
            }

            //Neu da co nha cai -> chuyen cai va sang phien xoc dia
            ConnectionHandler.Instance.Clients.Group(roomId.ToString()).changeBanker(_room.Banker);
            Thread.Sleep(2000);
            NotifyChangePhrase(SediePhrases.Shaking);
        }

        private bool InitSession()
        {
            try
            {
                if (!_room.IsEnough) throw new NotEnoughPlayerException("Chưa đủ người chơi.");

                _room.IsComfirming = true;
                _room.CountActivePlayer = 0;
                PlayerHandler.Instance.UpdateAll(new List<long>(_room.Players.Keys));

                foreach (var accountid in _room.Players.Keys)
                {
                    BettingGamePlayer player = _room.GetPlayer(accountid);
                    if (player == null)
                    {
                        continue;
                    }

                    if (!_room.CanBePlayer(player.Account))
                    {
                        _room.RemovePlayerWithReason(accountid, CommonClientMessage.NotEnoughMoneyToContinue);
                        continue;
                    }

                    if (player.IdleTime() < ConfigTimeout.MaxIdle) continue;
                    _room.RemovePlayerWithReason(accountid, CommonClientMessage.NoActionDuring(2));
                }

                if (!_room.IsEnough) throw new NotEnoughPlayerException("Chưa đủ người chơi.");
                List<SediePlayer> confirmList = new List<SediePlayer>(_room.Players.Values.Select(p => p as SediePlayer));
                long gameLoopId = BillingHandler.Instance.InitNewSession(confirmList, GameIdTypes.XOC_DIA,
                    _room.SessionId, _room.OwnerId, _room.MoneyType, _room.MinBet);

                if (gameLoopId < 1)
                {
                    throw new Exception("Tạo phiên không thành công.");
                }

                _room.PlayersInGame = new List<long>(confirmList.Select(p => p.AccountID));
                _room.IsPlaying = true;
                _room.IsComfirming = false;
                _room.LastStartGameLoopTime = DateTime.Now;
                _room.CountActivePlayer = _room.PlayersInGame.Count;
                _room.CurrentGameLoopId = gameLoopId;

                //Khoi tao game states va notify hanh dong cho nguoi choi
                InitGameStates(confirmList);
                NotifyEventAtStartGame();
                return true;
            }
            catch (NotEnoughPlayerException ex)
            {
                throw ex;
            }
            catch (Exception ex)
            {
                NLogManager.PublishException(ex);
            }
            finally
            {
                _room.IsComfirming = false;
            }
            return false;
        }

        /// <summary>
        /// Sau khi het thoi gian dang ky lam nha cai
        /// </summary>
        private void AfterRegisterPhrase()
        {
            //Neu ko co nguoi dang ki lam nha cai -> chuyen sang phien dang ki nha cai
            _room.Banker = -1;
            if (_registerQueue.IsEmpty)
            {
                NotifyChangePhrase(SediePhrases.Shaking);
                return;
            }

            //Neu da co nguoi dang ki lam nha cai -> duyet va kiem tra
            long bankerReq = PenaltyMoney.GetBankerReqMoney(_room.MinBet, _room.CountPlayers);
            while (!_registerQueue.IsEmpty)
            {
                long accountId;
                if (!_registerQueue.TryDequeue(out accountId)) continue;
                //Dang ki nhung ko co, next
                if (!_room.PlayersInGame.Contains(accountId)) continue;
                //Dang ki nhung ko du tien
                SediePlayer player = _room.GetPlayer(accountId) as SediePlayer;
                if (!player.Account.IsEnoughMoney(bankerReq, _room.MoneyType)) continue;

                _room.Banker = accountId;
                player.IsBanker = true;
                break;
            }

            //Neu van khong co nguoi du dieu kien lam cai
            if (_room.Banker < 1)
            {
                NotifyChangePhrase(SediePhrases.Shaking);
                return;
            }

            //Neu da co nha cai -> chuyen cai va sang phien xoc dia
            ConnectionHandler.Instance.Clients.Group(_room.SessionId.ToString()).changeBanker(_room.Banker);
            Thread.Sleep(2000);
            NotifyChangePhrase(SediePhrases.Shaking);
        }

        /// <summary>
        /// Sau khi het phien xoc dia
        /// </summary>
        private void AfterShakingPhrase()
        {
            //Sang phien dat cuoc
            NotifyChangePhrase(SediePhrases.Betting);
        }

        /// <summary>
        /// Het thoi gian dat cua
        /// </summary>
        private void AfterBettingPhrase()
        {
            //Neu khong co ai dat cua -> restart
            if (_betLogs.Count == 0)
            {
                RestartGame();
                return;
            }

            //so luong dong ngua
            Result = RandomUtil.NextInt(2) + RandomUtil.NextInt(2) + RandomUtil.NextInt(2) + RandomUtil.NextInt(2);
            //Neu co nha cai
            if (_room.Banker > 0)
            {
                NotifyChangePhrase(SediePhrases.RemoveBetting);
                return;
            }

            //Can cua
            List<SedieBetLog> even = null;
            _betLogs.TryGetValue(SedieGates.Even, out even);
            List<SedieBetLog> odd = null;
            _betLogs.TryGetValue(SedieGates.Odd, out odd);

            if (even == null)
            {
                RefundToClients(odd);
                RestartGame();
                return;
            }

            if (odd == null)
            {
                RefundToClients(even);
                RestartGame();
                return;
            }

            long evenTotal = even.Sum(log => log.Amount);
            long oddTotal = odd.Sum(log => log.Amount);
            if (evenTotal == oddTotal)
            {
                //Lock money
                if (!SendLockTransWithoutBanker()) RestartGame();
                NotifyChangePhrase(SediePhrases.OpenPlate);
                return;
            }

            //Can cua, tra tien cho nguoi choi
            List<SedieBetLog> refunds = evenTotal > oddTotal ? RemoveBetInGate(oddTotal, SedieGates.Even) : RemoveBetInGate(evenTotal, SedieGates.Odd);
            RefundToClients(refunds);
            if (!SendLockTransWithoutBanker()) RestartGame();
            NotifyChangePhrase(SediePhrases.OpenPlate);
            return;
        }

        /// <summary>
        /// Het thoi gian cai duoi cua
        /// </summary>
        private void AfterRemoveBettingPhrase()
        {
            //Nha cai ko duoi cua
            if (_forbiddenGates.IsEmpty)
            {
                if (!SendLockTransWithBanker()) RestartGame();
                NotifyChangePhrase(SediePhrases.OpenPlate);
                return;
            }

            //Nha cai duoi cua
            NotifyChangePhrase(SediePhrases.ReBetting);
        }

        private void AfterReBettingPhrase()
        {
            //Neu khong co ai dat cua -> restart
            if (_betLogs.Count == 0)
            {
                RestartGame();
                return;
            }

            if (!SendLockTransWithBanker()) RestartGame();
            NotifyChangePhrase(SediePhrases.OpenPlate);
        }

        private void AfterOpenPlatePhrase()
        {
            NotifyChangePhrase(SediePhrases.ShowResult);
        }

        private void AfterShowResultPhrase()
        {
            if (_room.Banker > 0 && (_quitBanker || _room.LeaveGameList.Contains(_room.Banker)))
            {
                _quitBanker = false;
                SediePlayer banker = _room.GetPlayer(_room.Banker) as SediePlayer;
                banker.IsBanker = false;
                _room.Banker = -1;
            }

            for (int i = 0; i < _room.PlayersInGame.Count; i++)
            {
                long accountId = _room.PlayersInGame[i];
                BettingGamePlayer player = _room.GetPlayer(accountId);
                if (!player.Account.IsEnoughMoney(PenaltyMoney.GetPlayerReqMoneyToCont(_room.MinBet), _room.MoneyType))
                    _room.RemovePlayerWithReason(accountId, CommonClientMessage.NotEnoughMoneyToContinue);
            }

            _room.RemovePlayerAfterEndGame();
            if (!_room.Players.ContainsKey(_room.Banker))
            {
                _quitBanker = false;
                _room.Banker = -1;
            }

            Refresh();
            if (_room.EnableStartGame()) NotifyChangePhrase(SediePhrases.Waiting);
        }

        #endregion step

        #region players_actions

        internal bool Bet(long accountId, long amount, SedieGates gate)
        {
            if (accountId < 1 || amount < _room.MinBet || !Enum.IsDefined(typeof(SedieGates), gate)) return false;
            if (!Monitor.TryEnter(_lock, ConfigTimeout.TryLockTimeOut)) return false;

            try
            {
                if (!CheckValidAction(accountId, SedieActions.Bet)) return false;
                if (!CheckValidGate(gate)) throw new InvalidActionException("Bạn không được đặt cửa này.");

                BettingGamePlayer player = _room.GetPlayer(accountId);
                if (!player.Account.IsEnoughMoney(amount, _room.MoneyType))
                    throw new InvalidActionException("Bạn không đủ tiền đặt cược");
                long maxBetMoney = PenaltyMoney.GetMaxMulti(gate) * _room.MinBet;
                if (amount > maxBetMoney) throw new InvalidActionException("Bạn đặt quá số tiền cho phép.");

                List<SedieBetLog> stack;
                if (_betLogs.TryGetValue(gate, out stack))
                {
                    if (!stack.Exists(p => p.AccountId == accountId))
                    {
                        _noBet++;
                        stack.Add(new SedieBetLog(accountId, amount, gate, _noBet));
                    }
                    else
                    {
                        long sum = stack.Where(log => log.AccountId == accountId).Sum(p => p.Amount);
                        if ((sum + amount) > maxBetMoney) throw new InvalidActionException("Bạn đặt quá số tiền cho phép.");
                        _noBet++;
                        stack.Add(new SedieBetLog(accountId, amount, gate, _noBet));
                    }
                }
                else
                {
                    stack = new List<SedieBetLog>();
                    _noBet++;
                    stack.Add(new SedieBetLog(accountId, amount, gate, _noBet));
                    _betLogs.TryAdd(gate, stack);
                }

                PlayerHandler.Instance.UpdateMoney(accountId, -amount, (GameMoneyType)_room.MoneyType);
                return true;
            }
            catch (Exception ex)
            {
                NLogManager.PublishException(ex);
            }
            finally
            {
                Monitor.Exit(_lock);
            }
            return false;
        }

        internal bool RemoveBet(long accountId, SedieGates gate)
        {
            if (_room.Banker < 1 || !Enum.IsDefined(typeof(SedieGates), gate)) return false;
            if (!Monitor.TryEnter(_lock, ConfigTimeout.TryLockTimeOut)) return false;

            try
            {
                if (!CheckValidAction(accountId, SedieActions.RemoveBet)) return false;
                if (!_betLogs.ContainsKey(gate)) return false;
                if (_forbiddenGates.Contains(gate)) return false;

                if (gate == SedieGates.Even && _forbiddenGates.Contains(SedieGates.Odd)) return false;
                if (gate == SedieGates.Odd && _forbiddenGates.Contains(SedieGates.Even)) return false;

                List<SedieBetLog> logs;
                if (!_betLogs.TryRemove(gate, out logs)) return false;
                _forbiddenGates.Push(gate);
                SedieState state;
                if (_states.TryGetValue(accountId, out state)) state.RemoveAction(SedieActions.BankerWeigh);
                RefundToClients(logs);
                return true;
            }
            catch (Exception ex)
            {
                NLogManager.PublishException(ex);
            }
            finally
            {
                Monitor.Exit(_lock);
            }
            return false;
        }

        internal bool BankerWeighGate(long accountId)
        {
            if (_room.Banker < 1) return false;
            if (!Monitor.TryEnter(_lock, ConfigTimeout.TryLockTimeOut)) return false;

            try
            {
                if (!CheckValidAction(accountId, SedieActions.BankerWeigh)) return false;
                SedieState state;
                if (_states.TryGetValue(accountId, out state))
                {
                    state.RemoveAction(SedieActions.BankerWeigh);
                    state.RemoveAction(SedieActions.RemoveBet);
                }
                StopTimer();
                AfterRemoveBettingPhrase();
                return true;
            }
            catch (Exception ex)
            {
                NLogManager.PublishException(ex);
            }
            finally
            {
                Monitor.Exit(_lock);
            }
            return false;
        }

        internal bool RegOrUnregBanker(long accountId, bool value)
        {
            if (accountId < 1) return false;
            if (!Monitor.TryEnter(_lock, ConfigTimeout.TryLockTimeOut)) return false;

            try
            {
                if (accountId != _room.Banker)
                {
                    if (value)
                    {
                        if (!CheckValidAction(accountId, SedieActions.RegisterBanker)) return false;
                        SediePlayer player = _room.GetPlayer(accountId) as SediePlayer;
                        if (player == null) return false;
                        if (!_room.CanBeBanker(player.Account)) return false;
                        if (_registerQueue.Contains(accountId)) return false;
                        _registerQueue.Enqueue(accountId);
                        return true;
                    }

                    if (!CheckValidAction(accountId, SedieActions.UnregisterBanker)) return false;
                    if (!_registerQueue.Contains(accountId)) return false;
                    List<long> temp = _registerQueue.ToList();
                    temp.Remove(accountId);
                    _registerQueue = new ConcurrentQueue<long>(temp);
                    return true;
                }

                if (!CheckValidAction(accountId, SedieActions.UnregisterBanker)) return false;
                _quitBanker = true;
                return true;
            }
            catch (Exception ex)
            {
                NLogManager.PublishException(ex);
            }
            finally
            {
                Monitor.Exit(_lock);
            }
            return false;
        }

        #endregion players_actions

        #region util

        private void SynchronizeAction(object state)
        {
            if (Elapsed > 0)
            {
                --Elapsed;
            }
        }

        internal void StopTimer()
        {
            Elapsed = 0;
            _roomTimer.Change(Timeout.Infinite, Timeout.Infinite);
            _synchronizedTimer.Change(Timeout.Infinite, Timeout.Infinite);
        }

        internal void StartTimer(int interval)
        {
            _roomTimer.Change(interval * 1000, Timeout.Infinite);
            Elapsed = interval;
            _synchronizedTimer.Change(0, 1000);
        }

        internal dynamic GetCurrentState(long accountId)
        {
            return null;
        }

        private void StopGamePlay(string reason)
        {
            StopTimer();
            if (_room == null) return;
            if (_room.CurrentGameLoopId > 0)
            {
                BillingHandler.Instance.CancelSessionByStopGame(_room.SessionId, _room.CurrentGameLoopId);
            }

            foreach (var key in _room.Players.Keys)
            {
                _room.RemovePlayerWithReason(key, reason);
            }
        }

        private void InitGameStates(List<SediePlayer> confirmList)
        {
            int count = confirmList.Count;
            _states = new ConcurrentDictionary<long, SedieState>();
            _results = new ConcurrentDictionary<long, SedieResult>();

            for (int i = 0; i < count; i++)
            {
                SediePlayer player = confirmList[i];
                player.Status = PlayerStatus.INGAME;
                if (_room.Banker == player.AccountID)
                {
                    player.IsBanker = true;
                }
                else
                {
                    player.IsBanker = false;
                }
                _states.TryAdd(player.AccountID, player.State);
                _results.TryAdd(player.AccountID, player.Result);
            }
        }

        private bool CheckValidAction(long accountId, SedieActions action)
        {
            SedieState state;
            if (!_states.TryGetValue(accountId, out state)) return false;
            if (state == null || state.Actions == null) return false;
            return state.Actions.Contains(action);
        }

        private bool CheckValidGate(SedieGates gate)
        {
            if (_forbiddenGates == null) return true;
            return !_forbiddenGates.Contains(gate);
        }

        private void NotifyEventAtStartGame()
        {
            if (_room.MoneyType != (byte)GameMoneyType.COIN) return;
            ThreadPool.QueueUserWorkItem(_ =>
            {
                try
                {
                    UtilHandler.Instance.NotifyEvents(_room.Players.Values.Select(p => p.AccountID));
                }
                catch (Exception ex)
                {
                    NLogManager.PublishException(ex);
                }
            });
        }

        internal void NotifyChangePhrase(SediePhrases phrase)
        {
            Phrase = phrase;
            int interval = ConfigTimeout.GetTimeout(phrase);
            switch (phrase)
            {
                case SediePhrases.Waiting:
                    break;
                case SediePhrases.RegisterBanker:
                    foreach (SedieState state in _states.Values)
                    {
                        state.AddNewAction(SedieActions.RegisterBanker);
                    }
                    break;
                case SediePhrases.Shaking:
                    //_registerQueue = new ConcurrentQueue<long>();
                    foreach (SedieState state in _states.Values)
                    {
                        if (state.AccountId == _room.Banker)
                        {
                            state.AddNewAction(SedieActions.UnregisterBanker);
                            continue;
                        }
                        state.AddNewAction(SedieActions.RegisterBanker);
                    }
                    break;
                case SediePhrases.Betting:
                    _noBet = -1;
                    _betLogs = new ConcurrentDictionary<SedieGates, List<SedieBetLog>>(Environment.ProcessorCount, 6);
                    _forbiddenGates = new ConcurrentStack<SedieGates>();
                    if (_room.Banker < 1)
                    {
                        _forbiddenGates.Push(SedieGates.FourDown);
                        _forbiddenGates.Push(SedieGates.FourUp);
                        _forbiddenGates.Push(SedieGates.ThreeDown);
                        _forbiddenGates.Push(SedieGates.ThreeUp);
                    }

                    foreach (SedieState state in _states.Values)
                    {
                        if (state.AccountId != _room.Banker) state.AddNewAction(SedieActions.Bet);
                    }
                    break;
                case SediePhrases.RemoveBetting:
                    foreach (SedieState state in _states.Values)
                    {
                        if (state.AccountId == _room.Banker)
                        {
                            state.AddNewAction(SedieActions.RemoveBet);
                            state.AddNewAction(SedieActions.BankerWeigh);
                            continue;
                        }
                        state.RemoveAction(SedieActions.Bet);
                    }
                    break;
                case SediePhrases.ReBetting:
                    foreach (SedieState state in _states.Values)
                    {
                        if (state.AccountId == _room.Banker)
                        {
                            state.RemoveAction(SedieActions.RemoveBet);
                            state.RemoveAction(SedieActions.BankerWeigh);
                            continue;
                        }
                        state.AddNewAction(SedieActions.Bet);
                    }
                    break;
                case SediePhrases.OpenPlate:
                    foreach (SedieState state in _states.Values)
                    {
                        if (state.AccountId == _room.Banker)
                        {
                            state.RemoveAction(SedieActions.RemoveBet);
                            state.RemoveAction(SedieActions.BankerWeigh);
                            continue;
                        }
                        state.RemoveAction(SedieActions.Bet);
                    }
                    break;
                case SediePhrases.ShowResult:
                    _tranLogs = new ConcurrentQueue<BettingGameTransaction>();
                    if (!CalculateResult()) throw new Exception("Loi tinh ket qua.");
                    break;
            }

            ThreadPool.QueueUserWorkItem(_ =>
            {
                if (phrase == SediePhrases.OpenPlate)
                {
                    Thread.Sleep(ConfigTimeout.RefundDelay * 1000);
                }

                foreach (long accountId in _room.Players.Keys)
                {
                    foreach (string connection in ConnectionHandler.Instance.GetConnections(accountId))
                    {
                        ConnectionHandler.Instance.Clients.Client(connection).startActionTimer(_room.CloneData(0), interval - ConfigTimeout.ClientDelay, phrase);
                    }
                }

                StartTimer(interval);
            });
        }

        #endregion util

        #region check_gate

        private void RefundToClients(List<SedieBetLog> logs)
        {
            if (logs == null) return;
            int count = logs.Count;
            if (count == 0) return;

            for (int i = 0; i < count; i++)
            {
                PlayerHandler.Instance.UpdateMoney(logs[i].AccountId, logs[i].Amount, (GameMoneyType)_room.MoneyType);
            }
            ConnectionHandler.Instance.Clients.Group(_room.ToString()).refundMoney(logs, BetLogs);
        }

        private List<SedieBetLog> RemoveBetInGate(long lowThreshold, SedieGates gate)
        {
            List<SedieBetLog> refunds = new List<SedieBetLog>();
            List<SedieBetLog> list;
            if (!_betLogs.TryGetValue(gate, out list)) return refunds;
            if (lowThreshold == list.Sum(p => p.Amount)) return refunds;

            int count = list.Count;
            long sum = 0;
            int idx = 0;

            for (int i = 0; i < count; i++)
            {
                sum += list[i].Amount;
                if (lowThreshold > sum) continue;
                idx = i;
                break;
            }

            if (idx < count - 1)
            {
                refunds.AddRange(list.GetRange(idx + 1, count - 1 - idx));
                list.RemoveRange(idx + 1, count - 1 - idx);
            }

            //truong hop vua du
            if (lowThreshold == sum)
            {
                if (list.Count == 0)
                {
                    _betLogs.TryRemove(gate, out list);
                }
                return refunds;
            }

            //truong hop le
            long change = sum - lowThreshold;
            list[idx].UpdateAmount(-change);
            refunds.Insert(0, new SedieBetLog(list[idx].AccountId, change, list[idx].Gate));
            if (list.Count == 0)
            {
                _betLogs.TryRemove(gate, out list);
            }
            return refunds;
        }

        private bool SendLockTransWithoutBanker()
        {
            //Lock money if banker's absent
            if (_room.Banker > 0)
            {
                return SendLockTransWithBanker();
            }

            _lockMoney = new ConcurrentDictionary<long, long>();
            foreach (List<SedieBetLog> list in _betLogs.Values)
            {
                list.ForEach(log => _lockMoney.AddOrUpdate(log.AccountId, log.Amount, (k, v) => v + log.Amount));
            }

            long result = BillingHandler.Instance.LockMoneyProcess(GameIdTypes.XOC_DIA, _room.SessionId,
                _room.CurrentGameLoopId, _lockMoney);
            if (result >= 0) return true;
            if (result == -51) throw new NotEnoughMoneyException();
            throw new Exception(string.Format("Huy phien: {0} - Ma loi: {1}", _room.CurrentGameLoopId, result));
        }

        private bool SendLockTransWithBanker()
        {
            //Lock money if banker's absent
            if (_room.Banker < 1)
            {
                return SendLockTransWithoutBanker();
            }

            BettingGamePlayer banker = _room.GetPlayer(_room.Banker);
            long bankerMoney = (_room.MoneyType == (byte)GameMoneyType.COIN
                ? banker.Account.Coin
                : banker.Account.TotalStar);
            if (SendLockTrans(bankerMoney)) return true;

            //Truong hop lech nhau
            //Can 4 ngua 4 sap
            long fourDown = GetSumInGate(SedieGates.FourDown);
            long fourUp = GetSumInGate(SedieGates.FourUp);
            List<SedieBetLog> listRefunds = fourDown > fourUp
                ? RemoveBetInGate(fourUp, SedieGates.FourDown)
                : RemoveBetInGate(fourDown, SedieGates.FourUp);

            if (SendLockTrans(bankerMoney))
            {
                RefundToClients(listRefunds);
                return true;
            }

            if (CheckAssizesLayer2(listRefunds, bankerMoney, SedieGates.FourDown, SedieGates.FourUp))
            {
                if (SendLockTrans(bankerMoney))
                {
                    RefundToClients(listRefunds);
                    return true;
                }
            }

            //Can 3 ngua 3 sap
            long threeDown = GetSumInGate(SedieGates.ThreeDown);
            long threeUp = GetSumInGate(SedieGates.ThreeUp);
            listRefunds.AddRange(threeDown > threeUp
                ? RemoveBetInGate(threeUp, SedieGates.ThreeDown)
                : RemoveBetInGate(threeDown, SedieGates.ThreeUp));

            if (SendLockTrans(bankerMoney))
            {
                RefundToClients(listRefunds);
                return true;
            }

            if (CheckAssizesLayer2(listRefunds, bankerMoney, SedieGates.ThreeDown, SedieGates.ThreeUp))
            {
                if (SendLockTrans(bankerMoney))
                {
                    RefundToClients(listRefunds);
                    return true;
                }
            }

            //Can chan le
            long odd = GetSumInGate(SedieGates.Odd);
            long even = GetSumInGate(SedieGates.Even);
            listRefunds.AddRange(odd > even
                ? RemoveBetInGate(even, SedieGates.Odd)
                : RemoveBetInGate(odd, SedieGates.Even));

            if (!SendLockTrans(bankerMoney)) return false;
            RefundToClients(listRefunds);
            return true;
        }

        private long GetCurrentAssizes()
        {
            long fourDown = GetSumInGate(SedieGates.FourDown);
            long fourUp = GetSumInGate(SedieGates.FourUp);
            long threeDown = GetSumInGate(SedieGates.ThreeDown);
            long threeUp = GetSumInGate(SedieGates.ThreeUp);
            long odd = GetSumInGate(SedieGates.Odd);
            long even = GetSumInGate(SedieGates.Even);

            return Math.Max(fourDown, fourUp) * PenaltyMoney.GetWinnerMulti(SedieGates.FourDown) - Math.Min(fourDown, fourUp)
                   + Math.Max(threeDown, threeUp) * PenaltyMoney.GetWinnerMulti(SedieGates.ThreeDown) - Math.Min(threeDown, threeUp)
                   + Math.Max(odd, even) * PenaltyMoney.GetWinnerMulti(SedieGates.Even) - Math.Min(odd, even);
        }

        private long GetSumInGate(SedieGates gate)
        {
            long sum = 0;
            List<SedieBetLog> logs;
            if (_betLogs.TryGetValue(gate, out logs)) sum = logs.Sum(p => p.Amount);
            return sum;
        }

        private long GetMaxLockBanker()
        {
            long max = 0;
            for (int i = 0; i < 5; i++)
            {
                SedieGates bigGate = 0;
                SedieGates smallGate = 0;
                switch (i)
                {
                    case 0:
                        bigGate = SedieGates.Even;
                        smallGate = SedieGates.FourDown;
                        break;
                    case 1:
                        bigGate = SedieGates.Odd;
                        smallGate = SedieGates.ThreeDown;
                        break;
                    case 2:
                        bigGate = SedieGates.Even;
                        break;
                    case 3:
                        bigGate = SedieGates.Odd;
                        smallGate = SedieGates.ThreeUp;
                        break;
                    case 4:
                        bigGate = SedieGates.Even;
                        smallGate = SedieGates.FourUp;
                        break;
                }
                long subtract = 0;
                foreach (SedieGates gate in Enum.GetValues(typeof(SedieGates)))
                {
                    if (gate == bigGate || gate == smallGate) continue;
                    subtract += GetSumInGate(gate);
                }

                long bigGateMoney = GetSumInGate(bigGate) * PenaltyMoney.GetWinnerMulti(bigGate);
                long smallGateMoney = GetSumInGate(smallGate) * PenaltyMoney.GetWinnerMulti(smallGate);
                long current = bigGateMoney + smallGateMoney - subtract;
                if (current > max)
                    max = current;
            }
            return max;
        }

        private bool SendLockTrans(long bankerMoney)
        {
            long current = GetCurrentAssizes();
            if (current > bankerMoney) return false;
            _lockMoney = new ConcurrentDictionary<long, long>();

            long lockBanker = GetMaxLockBanker();
            if (lockBanker > bankerMoney) return false;
            _lockMoney.TryAdd(_room.Banker, lockBanker);

            foreach (List<SedieBetLog> list in _betLogs.Values)
            {
                list.ForEach(log => _lockMoney.AddOrUpdate(log.AccountId, log.Amount, (k, v) => v + log.Amount));
            }

            long result = BillingHandler.Instance.LockMoneyProcess(GameIdTypes.XOC_DIA, _room.SessionId,
                _room.CurrentGameLoopId, _lockMoney);
            _restartCount = 0;
            if (result >= 0) return true;
            if (result == -51) throw new NotEnoughMoneyException();
            throw new Exception(string.Format("Huy phien: {0} - Ma loi: {1}", _room.CurrentGameLoopId, result));
        }

        private bool CheckAssizesLayer2(List<SedieBetLog> listRefunds, long bankerMoney, SedieGates gate1, SedieGates gate2)
        {
            long moneyInGate1 = GetSumInGate(gate1);
            long moneyInGate2 = GetSumInGate(gate2);
            if (moneyInGate1 <= 0 || moneyInGate2 <= 2) return false;
            int multi = PenaltyMoney.GetWinnerMulti(gate1);
            multi = multi > 1 ? (multi - 1) : 1;

            long currentAssizes = GetCurrentAssizes();
            if (currentAssizes <= bankerMoney) return true;
            long decrease = (currentAssizes - bankerMoney) / multi;

            decrease = (decrease % _room.MinBet) == 0
                ? decrease
                : (decrease / _room.MinBet + 1) * _room.MinBet;

            long lowThreshold = moneyInGate1 > decrease ? (moneyInGate1 - decrease) : moneyInGate1;
            listRefunds.AddRange(RemoveBetInGate(lowThreshold, gate1));
            listRefunds.AddRange(RemoveBetInGate(lowThreshold, gate2));
            return true;
        }

        private bool CalculateResult()
        {
            SedieGates bigGate = 0;
            SedieGates smallGate = 0;
            switch (Result)
            {
                case 0:
                    bigGate = SedieGates.Even;
                    smallGate = SedieGates.FourDown;
                    break;
                case 1:
                    bigGate = SedieGates.Odd;
                    smallGate = SedieGates.ThreeDown;
                    break;
                case 2:
                    bigGate = SedieGates.Even;
                    break;
                case 3:
                    bigGate = SedieGates.Odd;
                    smallGate = SedieGates.ThreeUp;
                    break;
                case 4:
                    bigGate = SedieGates.Even;
                    smallGate = SedieGates.FourUp;
                    break;
            }

            //Khong co nha cai
            if (_room.Banker < 1)
            {
                EnqueueTransWithoutBanker(bigGate);
            }
            else
            {
                EnqueueTransWithBanker(bigGate, smallGate);
            }

            bool finishResult = BillingHandler.Instance.FinishSession(_tranLogs, GameIdTypes.XOC_DIA, _room.SessionId,
                _room.CurrentGameLoopId, _room.Players, _room.MoneyType, _lockMoney);

            if (!finishResult)
            {
                //Thanh toan loi
                ConnectionHandler.Instance.Clients.Group(_room.SessionId.ToString())
                    .message(CommonClientMessage.BillingError, 3);
                BillingHandler.Instance.CancelSessionByStopGame(_room.SessionId, _room.CurrentGameLoopId);
                return false;
            }

            _room.LastStartGameLoopTime = DateTime.Now;
            if (_room.History.Count == PenaltyMoney.MaxHistory)
            {
                _room.History.RemoveAt(0);
            }
            _room.History.Add(Result);
            foreach (SedieResult result in _results.Values)
            {
                if (result.Revenue <= 0) continue;
                long real = result.Revenue - (long)Math.Floor(result.Revenue * _room.MoneyPercent);
                result.UpdateRevenue(-real);
                PlayerHandler.Instance.UpdateMoney(result.AccountId, -real, (GameMoneyType)_room.MoneyType);
            }
            return true;
        }

        private void EnqueueTransWithoutBanker(SedieGates bigGate)
        {
            foreach (List<SedieBetLog> logs in _betLogs.Values)
            {
                foreach (SedieBetLog log in logs)
                {
                    if (log.Gate == bigGate)
                    {
                        int multi = PenaltyMoney.GetWinnerMulti(bigGate);
                        EnqueueTrans(log.AccountId, log.Amount * (multi + 1), string.Format("Thang cua {0}", log.Gate));
                        EnqueueTrans(log.AccountId, -log.Amount, string.Format("Doi ung cua {0}", log.Gate));
                        long detail = log.Amount * (multi + 1);
                        PlayerHandler.Instance.UpdateMoney(log.AccountId, detail, (GameMoneyType)_room.MoneyType);

                        SedieResult result;
                        if (!_results.TryGetValue(log.AccountId, out result)) continue;
                        result.Details.Add(new SedieBetLog(log.AccountId, detail, log.Gate));
                        continue;
                    }
                    EnqueueTrans(log.AccountId, -log.Amount, string.Format("Thua cua {0}", log.Gate));
                }
            }
        }

        private void EnqueueTransWithBanker(SedieGates bigGate, SedieGates smallGate)
        {
            long bankerFee = 0; //phi cua nguoi than cho nha cai
            long totalLose = 0; //tong tien thua cua nhung nguoi thua
            long totalWin = 0; //tong tien thang cua nhung nguoi thang

            foreach (List<SedieBetLog> logs in _betLogs.Values)
            {
                foreach (SedieBetLog log in logs)
                {
                    if (log.Gate == bigGate || log.Gate == smallGate)
                    {
                        long total = log.Amount * PenaltyMoney.GetWinnerMulti(log.Gate);
                        long realRevenue = (long)Math.Floor(PenaltyMoney.RevenuePercent * total);

                        totalWin += total;
                        bankerFee += (total - realRevenue);
                        long detail = log.Amount + realRevenue;
                        EnqueueTrans(log.AccountId, detail, string.Format("Thang cua {0}", log.Gate));
                        EnqueueTrans(log.AccountId, -log.Amount, string.Format("Thang cua {0}", log.Gate));
                        PlayerHandler.Instance.UpdateMoney(log.AccountId, detail, (GameMoneyType)_room.MoneyType);

                        SedieResult result;
                        if (!_results.TryGetValue(log.AccountId, out result)) continue;
                        result.Details.Add(new SedieBetLog(log.AccountId, detail, log.Gate));
                        if (!_results.TryGetValue(_room.Banker, out result)) continue;
                        result.Details.Add(new SedieBetLog(_room.Banker, total - realRevenue, log.Gate));
                        continue;
                    }
                    //Nhung nguoi thua
                    EnqueueTrans(log.AccountId, -log.Amount, string.Format("Thua cua {0}", log.Gate));
                    totalLose += log.Amount;
                }
            }

            EnqueueTrans(_room.Banker, bankerFee, "Phi cua nguoi thang cho nha cai.");
            //PlayerHandler.Instance.UpdateMoney(_room.Banker, bankerFee, (GameMoneyType)_room.MoneyType);
            long temp = totalLose - totalWin;
            if (temp < 0)
            {
                EnqueueTrans(_room.Banker, temp, "Nha cai huong chenh lech thang thua.");//duong thi lai, am thi thua
            }
            else
            {
                long value = 0;
                _lockMoney.TryGetValue(_room.Banker, out value);
                EnqueueTrans(_room.Banker, temp + value, "Nha cai huong chenh lech thang thua.");//duong thi lai, am thi thua
                EnqueueTrans(_room.Banker, -value, "Nha cai huong chenh lech thang thua.");
            }

            PlayerHandler.Instance.UpdateMoney(_room.Banker, bankerFee + totalLose - totalWin, (GameMoneyType)_room.MoneyType);
        }

        private void EnqueueTrans(long accountId, long change, string des)
        {
            if (change == 0) return;
            SediePlayer player = _room.GetPlayer(accountId) as SediePlayer;
            _tranLogs.Enqueue(new BettingGameTransaction
            {
                AccountId = accountId,
                Username = player.Account.UserName,
                MoneyType = _room.MoneyType,
                Action = 1,
                Amount = change,
                Description = des
            });

            player.Result.UpdateRevenue(change);
        }

        #endregion check_gate

        #region dispose

        internal void Refresh()
        {
            StopTimer();
            Phrase = SediePhrases.Waiting;
            _room.IsPlaying = false;
            _room.IsComfirming = false;
            _lockMoney = new ConcurrentDictionary<long, long>();
            _states = new ConcurrentDictionary<long, SedieState>();
            _betLogs = new ConcurrentDictionary<SedieGates, List<SedieBetLog>>();
            _forbiddenGates = new ConcurrentStack<SedieGates>();
            _results = new ConcurrentDictionary<long, SedieResult>();
            _tranLogs = new ConcurrentQueue<BettingGameTransaction>();
            _noBet = -1;
            _quitBanker = false;

            foreach (var key in _room.Players.Keys)
            {
                BettingGamePlayer player = null;
                if (!_room.Players.TryGetValue(key, out player)) continue;
                (player as SediePlayer).RefreshData();
            }
        }

        public void Dispose()
        {
            Refresh();
            _room = null;
            _roomTimer.Dispose();
            _synchronizedTimer.Dispose();
        }

        private void RestartGame()
        {
            _restartCount++;
            if (_restartCount >= ConfigTimeout.RestartCount)
            {
                _restartCount = 0;
                StopGamePlay("Không đặt cửa 3 ván liên tục.");
                return;
            }

            if (_room.Banker > 0 && (_quitBanker || _room.LeaveGameList.Contains(_room.Banker)))
            {
                _quitBanker = false;
                SediePlayer banker = _room.GetPlayer(_room.Banker) as SediePlayer;
                banker.IsBanker = false;
                _room.Banker = -1;
            }

            for (int i = 0; i < _room.PlayersInGame.Count; i++)
            {
                long accountId = _room.PlayersInGame[i];
                BettingGamePlayer player = _room.GetPlayer(accountId);
                if (!player.Account.IsEnoughMoney(PenaltyMoney.GetPlayerReqMoneyToCont(_room.MinBet), _room.MoneyType))
                    _room.RemovePlayerWithReason(accountId, CommonClientMessage.NotEnoughMoneyToContinue);
            }

            _room.RemovePlayerAfterEndGame();
            if (!_room.Players.ContainsKey(_room.Banker))
            {
                _quitBanker = false;
                _room.Banker = -1;
            }

            Refresh();
            if (_room.EnableStartGame()) NotifyChangePhrase(SediePhrases.Waiting);
        }

        #endregion dispose
    }
}